﻿using System;
using System.Collections.Generic;
using System.Threading;
using SharpSvn;
using SharpSvn.Security;
using Util.Net;
using System.ComponentModel;

namespace svnbox {

    public class SyncStatusArgs : EventArgs {
        public long REPO_version = 0;
        public long WC_version = 0;
        public SyncStatusArgs(long wc, long repo) {
            WC_version = wc;
            REPO_version = repo;
        }
    }

    public enum SvnCommandType {
        Commit, Add, Update, Delete, Rename, ResolveWorking,
        ResolveConflictPostpone, ResolvePostponeAdd,
        ResolvePostponeModified
            , ForceDelete
    }

    public class Svn {

        public Thread thread;
        public string User;
        public string Pass;
        public string Host;
        public string RootPath;

        public bool NetworkAvailable = false;

        public bool IsRunning = false;

        const int DELAID_MONITOR = 10000;
        const int DELAID_COMMIT = 1000;//1000;
        const int DELAID_BALLOON = 1000;

        public SvnCommand Command;

        public delegate void SyncStatusHandler(object myObject, SyncStatusArgs myArgs);
        public event SyncStatusHandler OnSyncStatusChange;
        protected void DoCheckStatus(object sender, SyncStatusArgs e) {
            if (OnSyncStatusChange != null) {
                OnSyncStatusChange(this, e);
            }
        }

        public delegate void AuthStatusHandler(object myObject, EventArgs myArgs);
        public event AuthStatusHandler OnAuthStatusChange;
        protected void DoAuthStatus(object sender, EventArgs e) {
            if (OnAuthStatusChange != null) {
                OnAuthStatusChange(this, e);
            }
        }

        public delegate void NetworkStatusHandler(object myObject, EventArgs myArgs);
        public event NetworkStatusHandler OnNetworkStatusChange;
        protected void DoNetworkStatus(object sender, EventArgs e) {
            if (OnNetworkStatusChange != null) {
                OnNetworkStatusChange(this, e);
            }
        }


        private static void debug(string s) { System.Diagnostics.Debug.WriteLine("{Svn} " + s); }
        private static void debug(string source, Exception s) {
            System.Diagnostics.Debug.WriteLine("{Svn} " + source + " " + s.Message);
            if (s.InnerException != null) {
                System.Diagnostics.Debug.WriteLine("{Svn} " + source + " InnerEx : " + s.InnerException.Message);
            }
        }

        private static bool Locked = false;

        public Svn() {
            Host = App.Profile.Get("svn", "repository");
            User = App.Profile.Get("svn", "user");
            Pass = App.Profile.Get("svn", "password");
            RootPath = App.Profile.Get("wc", "path");

            SetupSvnCommand();

            DoNetworkStatusChanged(null, null);
            NetworkStatus.AvailabilityChanged += new NetworkStatusChangedHandler(DoNetworkStatusChanged);
        }

        public void SetupSvnCommand() {
            if (Command != null) {
                Command = null;
                GC.Collect();
            }
            Command = new SvnCommand();
            Command.Host = Host;
            Command.User = User;
            Command.Pass = Pass;
            Command.RootPath = RootPath;
            Command.NetworkAvailable = NetworkAvailable;

        }

        public void DoNetworkStatusChanged(object sender, NetworkStatusChangedArgs e) {
            if (!NetworkStatus.IsAvailable) {
                debug("{Network} Is disconnected}");
            } else {
                debug("{Network} Is now connected}");
            }
            NetworkAvailable = NetworkStatus.IsAvailable;
            Command.NetworkAvailable = NetworkStatus.IsAvailable;

            DoNetworkStatus(this, e);
        }

        public void Start() {
            //debug("Start CleanUp..");
            Command.CleanUp();
            Command.CleanUp();
            thread = new Thread(new ThreadStart(Monitor));
            thread.Name = "Svn.Start";
            thread.Start();
        }

        //Loop to Check for new HEAD revision every few seconds !
        public void Monitor() {
            IsRunning = true;
            do {
                _Start();
                Thread.Sleep(DELAID_MONITOR);
            } while (IsRunning);
        }

        public void _Start() {
            if (!NetworkAvailable)
                return;
            if (!Command.IsWorkingCopy(RootPath)) {
                Command.CheckOut();
                return;
            }
            long HEAD = 0, WC = 0;

            //While lock dont do nothing
            if (Locked) {
                debug("_Start() return .Root folder is locked...");
                App.SetSyncingIcon();
                return;
            }

            if (Command.CleanUp()) {
                //debug("Cleanup success..");
            } else {
                debug("Cleanup failed !.");
                return;
            }

            try {
                HEAD = Command.Info(new Uri(Host)).LastChangeRevision;
            } catch (Exception ex) {
                debug("{_Start} " + ex.Message);
                return;
            }

            try {

                WC = Command.Info(RootPath).LastChangeRevision;

                if (HEAD > WC) {
                  //  debug("HEAD>>WC SyncToHead  " + string.Format("WC:{0} R:{1}", WC, HEAD));

                    //Optimize me
                    DoCheckStatus(this, new SyncStatusArgs(WC, HEAD));

                    SyncToHead(); // To The Local WC
                } else {

                    //Optimize me
                    DoCheckStatus(this, new SyncStatusArgs(WC, HEAD));

                 //   debug("HEAD==WC SyncFromHead  WC/R : " + HEAD.ToString());
                    SyncFromHead();//To The Remote
                }
            } catch (SvnException exv) {
                debug("{_Start+Sync.exv} " + exv.SvnErrorCode.ToString() + " " + exv.Message + " Svn.Url :" + Host);
                if (exv.SvnErrorCode == SvnErrorCode.SVN_ERR_RA_UUID_MISMATCH) {
                    //Realocate or clean checkout
                    debug("fatal error SVN_ERR_RA_UUID_MISMATCH");
                    App.ShowFatalError("SVN_ERR_RA_UUID_MISMATCH"); //
                    Stop();
                }
            } catch (Exception ex) {
                debug("{_Start+Sync} " + ex.Message + " Svn.Url :" + Host);
            }
        }

        public void Stop() {
            try {
                if (thread != null)
                    thread.Abort();
                if (balloon_timer != null)
                    balloon_timer.Change(-1, -1);
                if (commit_timer != null)
                    commit_timer.Change(-1, -1);
            } catch { }
            thread = null;
            IsRunning = false;
        }

        public bool doCommand(SvnCommandType t, string p, bool doBalloon, bool doCommit) {
            bool oparation_success = false;

            switch (t) {
                case SvnCommandType.Commit:
                   // debug("{doCommand} {Commit...} " + p  );
                    Locked = true;
                    if (Command.Commit(p)) { oparation_success = true; } break;
                case SvnCommandType.Delete: if (Command.Delete(p)) { oparation_success = true; } break;
                case SvnCommandType.ForceDelete: if (Command.ForceDelete(p)) { oparation_success = true; } break;
                case SvnCommandType.ResolveWorking: if (Command.Resolve(p, SvnAccept.Working)) { oparation_success = true; } break;
                case SvnCommandType.ResolveConflictPostpone:
                case SvnCommandType.ResolvePostponeAdd:
                case SvnCommandType.ResolvePostponeModified: if (Command.Resolve(p, SvnAccept.Postpone)) { oparation_success = true; } break;
                case SvnCommandType.Add: if (Command.Add(p)) { oparation_success = true; } break;
                case SvnCommandType.Update:
                   // debug("{doCommand} {Update...} " + p);
                    Locked = true;
                    if (Command.Update(p)) { oparation_success = true; } break;
                case SvnCommandType.Rename: if (Command.Rename(p.Split('|')[0], p.Split('|')[1])) { oparation_success = true; } break;
            }
            Locked = false;
           // debug("{doCommand} {" + t.ToString() + "} " + p + " : " + (oparation_success ? "Success" : "Faild"));
            if (oparation_success == true) {
                if (doBalloon) {
                    DelaiedBalloon(t.ToString() + " : " + p);
                }
                if (doCommit) {
                    DelaiedCommit(t.ToString() + " : " + p);
                }
                return true;
            }
            return false;
        }

        public void SyncFromHead() {
            using (SvnClient cl = Command.Client()) {
                if (cl == null)
                    return;
                cl.Status(RootPath, new SvnStatusArgs {
                    Depth = SvnDepth.Infinity, ThrowOnError = true,
                    RetrieveRemoteStatus = true, Revision = SvnRevision.Head
                }
                            , new EventHandler<SvnStatusEventArgs>(delegate(object sender, SvnStatusEventArgs e) {
                    debug(string.Format("Changes 'WC:{0} R:{1}- {2}' -- ", e.LocalContentStatus, e.RemoteContentStatus, e.FullPath));

                    switch (e.LocalContentStatus) {
                        case SvnStatus.None:
                            if (e.RemoteContentStatus == SvnStatus.None) {
                                doCommand(SvnCommandType.ResolveWorking, e.FullPath, false, false);
                            } else {
                                debug("NOHANDLE2");
                            }
                            break;
                        case SvnStatus.Normal:
                            if (e.RemoteContentStatus == SvnStatus.None) {
                                doCommand(SvnCommandType.Update, e.FullPath, true, false);
                            } else {
                                debug("NOHANDLE1 ");
                            }
                            break;
                        case SvnStatus.NotVersioned: doCommand(SvnCommandType.Add, e.FullPath, false, true); break;
                        case SvnStatus.Deleted:
                        case SvnStatus.Missing: doCommand(SvnCommandType.ForceDelete, e.FullPath, false, true); break;
                        case SvnStatus.Modified: DelaiedCommit("Modified " + e.FullPath); break;
                        case SvnStatus.Added: DelaiedCommit("Add " + e.FullPath); break;
                        case SvnStatus.Conflicted: doCommand(SvnCommandType.ResolveConflictPostpone, e.FullPath, false, true); break;
                        default:
                            debug(string.Format("NOHANDLE  'WC:{0} R:{1}' -- {2}", e.LocalContentStatus, e.RemoteContentStatus, e.FullPath));
                            break;
                    }
                }));
            }
        }

        public List<string> ChangedFiles = new List<string>();
        public void GetFileStatus() {
            ChangedFiles.Clear();
            using (SvnClient cl = Command.Client()) {
                if (cl == null)
                    return;
                cl.Status(RootPath, new SvnStatusArgs { Depth = SvnDepth.Infinity, ThrowOnError = true }
                , new EventHandler<SvnStatusEventArgs>(delegate(object sender, SvnStatusEventArgs e) {

                    ChangedFiles.Add(e.FullPath);
                    /*
                    switch (e.LocalContentStatus) {
                        case SvnStatus.Normal:break;
                        case SvnStatus.None: break;
                        case SvnStatus.NotVersioned: break;
                        case SvnStatus.Added:break;
                        case SvnStatus.Missing: break;
                        case SvnStatus.Modified: break;
                        case SvnStatus.Conflicted: break;
                        default: break;
                    }*/

                }));
            }
        }
        public void SyncToHead() {
            using (SvnClient cl = Command.Client()) {
                if (cl == null)
                    return;
                cl.Status(RootPath, new SvnStatusArgs { Depth = SvnDepth.Infinity, ThrowOnError = true, RetrieveRemoteStatus = true }
                , new EventHandler<SvnStatusEventArgs>(delegate(object sender, SvnStatusEventArgs e) {

                    debug(string.Format("*Changes* 'WC:{0} R:{1}' -{2}", e.LocalContentStatus, e.RemoteContentStatus, e.FullPath));

                    switch (e.LocalContentStatus) {
                        case SvnStatus.Normal:
                            if (e.RemoteContentStatus == SvnStatus.Modified) {
                                if (e.FullPath == RootPath) {
                                    debug("* Root WC.Normal R.Modified : do nothing An root update will happen at the end");
                                } else {
                                    doCommand(SvnCommandType.Update, e.FullPath, true, false);
                                }
                            } else if (e.RemoteContentStatus == SvnStatus.Deleted) {
                                doCommand(SvnCommandType.Delete, e.FullPath, true, false);
                            } else {
                                debug(string.Format("* NOHANDLE 2 'WC:{0} R:{1}' -- {2}", e.LocalContentStatus
                                    , e.RemoteContentStatus, e.FullPath));
                            }
                            break;
                        case SvnStatus.None: doCommand(SvnCommandType.Update, e.FullPath, true, false); break;
                        case SvnStatus.NotVersioned: doCommand(SvnCommandType.Add, e.FullPath, false, true); break;
                        case SvnStatus.Added:
                            if (e.RemoteContentStatus == SvnStatus.Added) {
                                debug("Warning !!! An a file has been Added outside your working copy and you have Added to the same file");
                                doCommand(SvnCommandType.ResolvePostponeAdd, e.FullPath, false, true);
                            } else {
                                DelaiedCommit("Add " + e.FullPath);
                            } break;
                        case SvnStatus.Missing:
                            doCommand(SvnCommandType.Delete, e.FullPath, false, true);
                            break;
                        case SvnStatus.Modified:
                            if (e.RemoteContentStatus == SvnStatus.Modified) {
                                debug("Warning !!! An a file has been modified outside your working copy and you have made changes to the same file");
                                doCommand(SvnCommandType.ResolvePostponeModified, e.FullPath, false, true);
                            } else {
                                DelaiedCommit("Modified " + e.FullPath);
                            } break;
                        case SvnStatus.Conflicted: doCommand(SvnCommandType.ResolveConflictPostpone, e.FullPath, false, false); break;
                        case SvnStatus.Deleted : doCommand(SvnCommandType.ForceDelete,e.FullPath,false,false); break;
                        default: debug(string.Format("* NOHANDLE 'WC:{0} R:{1}' -- {2}", e.LocalContentStatus, e.RemoteContentStatus, e.FullPath));
                            break;
                    }

                }));
                debug("SyncToHead run sucessfuly");

                // After fixing All Possible Conflict  Perform An update To get the HEAD revision
                doCommand(SvnCommandType.Update, RootPath, false, false);
            }
        }
        public bool IsAuthentication = false;

        public void CheckAuthentication_Worker(string _Repository, string _User, string _Pass) {
            try {
                SvnClient cl = new SvnClient();
                cl.Authentication.Clear();
                cl.Authentication.SslServerTrustHandlers += delegate(object sender, SvnSslServerTrustEventArgs e) {
                    e.AcceptedFailures = e.Failures;
                    e.Save = true;
                };
                cl.Authentication.UserNamePasswordHandlers += delegate(object sender, SvnUserNamePasswordEventArgs e) {
                    e.UserName = _User;
                    e.Password = _Pass;
                    e.Save = false;
                };
                SvnInfoEventArgs inf;
                cl.GetInfo(SvnTarget.FromUri(new Uri(_Repository)), out inf);
                IsAuthentication = true;
                DoAuthStatus(null, null);
                // return true;
            } catch (Exception ex) {
                debug("{CheckAuthentication}", ex);
                IsAuthentication = false;
                DoAuthStatus(null, null);
                // return false;
            }
        }
        public void CheckAuthentication(string _Repository, string _User, string _Pass) {

            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerSupportsCancellation = true;
            bw.WorkerReportsProgress = true;
            bw.DoWork += new DoWorkEventHandler(
                delegate { CheckAuthentication_Worker(_Repository, _User, _Pass); }
                );
            if (bw.IsBusy != true) {
                bw.RunWorkerAsync();
            }
        }

        public static bool CheckWcUri(string _WC, string _URI) {
            try {
                SvnClient cl = new SvnClient();
                cl.Authentication.Clear();
                Uri wc = cl.GetUriFromWorkingCopy(_WC);
                Uri uri = new Uri(_URI);
                if (wc == null) {
                    //working copy dosent have any conflict configurations 
                    return true;
                }
                if (uri.AbsoluteUri == wc.AbsoluteUri) {
                    return true;
                } else {
                    debug("{CheckWcUri} WC has Diffrent config for another path " + uri.AbsoluteUri + "!=" + wc.AbsoluteUri);
                    return false;
                }
            } catch (Exception ex) {
                debug("{CheckWcUri}" + ex.Message);
                return false;
            }
        }

        public static string GetUriFromWC(string _WC) {
            try {
                SvnClient cl = new SvnClient();
                cl.Authentication.Clear();
                Uri wc = cl.GetUriFromWorkingCopy(_WC);
                return wc.AbsoluteUri;
            } catch {
                return "";
            }
        }

        public static void CreateRepository(string repoPath) {
            using (var svnRepoClient = new SvnRepositoryClient()) {
                svnRepoClient.LoadConfiguration(repoPath);
                svnRepoClient.CreateRepository(repoPath);
            }
        }

        #region Delaid balloon

        public void DelaiedBalloon(string arg = "") {
            balloon_args.Add(arg);
            balloon_timer.Change(DELAID_BALLOON, Timeout.Infinite);
        }
        public static void ballonworker(List<string> collection) {
            string Changes = string.Join("\r\n", collection.ToArray());
            App.MainForm.BalloonTip(Changes.Replace(App.Svn.RootPath, ""));
        }
        public static List<string> balloon_args = new List<string>();
        public Timer balloon_timer = new Timer(delegate(Object state) {
            ballonworker(balloon_args);
            balloon_args.Clear();
        });

        #endregion


        #region Delaid commit

        public void DelaiedCommit(string arg = "") {
            commit_args.Add(arg);
            commit_timer.Change(DELAID_COMMIT, Timeout.Infinite);
        }
        public static void commitworker(List<string> collection) {
            string Changes = string.Join("\r\n", collection.ToArray());
            debug("{DelaidCommit} Commiting...");
            App.Svn.doCommand(SvnCommandType.Commit, Changes, false, false);
            App.Svn.doCommand(SvnCommandType.Update, App.Svn.RootPath, false, false); // Is root dont display baloon
        }
        public static List<string> commit_args = new List<string>();
        public Timer commit_timer = new Timer(delegate(Object state) {
            commitworker(commit_args);
            commit_args.Clear();
        });

        #endregion


    }
}
